﻿using Red.Core;
using System;
using System.Collections.ObjectModel;

namespace Red.Controls.ViewModels
{
    /// <summary>
    /// Monitors transfer connection with the app.
    /// </summary>
    public sealed class AppMonitorTransferViewModel : ViewModel
    {
        #region Data

        private int _maxSamplesCount = 800;
        private uint _invertedScale = 100;
        private ObservableCollection<Sample> _samples = new ObservableCollection<Sample>();
        private string _totalData = "Total: unknown";
        private string _perSecData = "Transfer: unknown";

        /// <summary>
        /// Used to keep track of new sample delta value.
        /// </summary>
        private ulong _previousData = 0;

        /// <summary>
        /// Samples visualization interval (width of the sample).
        /// </summary>
        private const int _interval = 2;

        #endregion

        #region Properties

        /// <summary>
        /// Maximum number of samples stored. When overload, first samples are removed.
        /// </summary>
        public int MaxSamplesCount
        {
            get { return _maxSamplesCount; }
            set
            {
                _maxSamplesCount = value;
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// Inverted scale of the samples visualization.
        /// </summary>
        public uint InvertedScale
        {
            get { return _invertedScale; }
            set
            {
                uint previousInvertedScale = _invertedScale;
                _invertedScale = value;
                if ( _invertedScale == 0 )
                    _invertedScale = 1;

                RescaleSamples( previousInvertedScale, _invertedScale );
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// Collection of samples.
        /// </summary>
        public ObservableCollection<Sample> Samples { get { return _samples; } }

        /// <summary>
        /// Total data reported so far.
        /// </summary>
        public string TotalData
        {
            get { return _totalData; }
            private set
            {
                _totalData = value;
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// Data reported per second.
        /// </summary>
        public string PerSecData
        {
            get { return _perSecData; }
            private set
            {
                _perSecData = value;
                OnPropertyChanged();
            }
        }

        #endregion

        #region Internal Classes

        /// <summary>
        /// Sample data.
        /// </summary>
        public class Sample
        {
            public uint Interval { get; set; }
            public uint Value { get; set; }
            public DateTime Time { get; set; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds sample to the sample collection.
        /// </summary>
        /// <param name="time"></param>
        /// <param name="value"></param>
        public void AddSample( DateTime time, ulong value )
        {
            ulong sampleValue = value - _previousData;

            ulong totalData;
            string totalDataUnit;
            TranslateToProperUnits( value, out totalData, out totalDataUnit );

            Sample sample = new Sample();
            sample.Interval = _interval;
            sample.Value = (uint)(sampleValue / InvertedScale);
            sample.Time = time;

            Samples.Add( sample );
            if ( Samples.Count > MaxSamplesCount )
            {
                Samples.RemoveAt( 0 );
            }

            _previousData = value;

            TotalData = "Total: " + totalData + " " + totalDataUnit;
            PerSecData = "Transfer: " + sampleValue * 8 + " B";
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Helper method for translating bytes to kilobytes etc.
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="value"></param>
        /// <param name="unit"></param>
        private void TranslateToProperUnits( ulong bytes, out ulong value, out string unit )
        {
            unit = "B";
            value = bytes;

            ulong data = bytes;
            data /= 1024;
            if ( data > 0 )
            {
                unit = "kB";
                value = data;
            }

            data /= 1024;
            if( data > 0 )
            {
                unit = "mB";
                value = data;
            }

            data /= 1024;
            if ( data > 0 )
            {
                unit = "gB";
                value = data;
            }
        }

        /// <summary>
        /// Helper method for samples rescaling.
        /// </summary>
        /// <param name="previousInvertedScale"></param>
        /// <param name="currentInvertedScale"></param>
        private void RescaleSamples( uint previousInvertedScale, uint currentInvertedScale )
        {
            if ( previousInvertedScale == 0 || currentInvertedScale == 0 )
                return;

            foreach ( var sample in Samples )
            {
                sample.Value *= previousInvertedScale / currentInvertedScale;
            }
        }

        #endregion

    }
}
